<template>
  <el-empty v-if="!!emptyText" :style="calcStyleAndWidth" :description="emptyText">
  </el-empty>
  <div v-else-if="!hidden" :class="[calcClass, 'yv-tree', 'yv-tree-wrapper']" :style="calcStyle">
    <el-row v-show="enableFilter || enableRefresh || enableExpandedAll">
      <el-input v-show="enableFilter" :placeholder="locale.components.tree.filter" v-model="_filterKeyword"/>
      <el-button-group class="ml-4" v-show="enableRefresh || enableExpandedAll">
        <el-button v-show="enableRefresh" :icon="renderIcon('Refresh')" @click="reload"/>
        <el-button v-show="enableExpandedAll" :icon="renderIcon('fa ArrowsAltV')" @click="expandedAll(true)"/>
        <el-button v-show="enableExpandedAll" :icon="renderIcon('antd VerticalAlignMiddleOutlined')"
                   @click="expandedAll(false)"/>
      </el-button-group>
    </el-row>
    <el-scrollbar>
      <el-tree
          v-loading="loading"
          style="flex-grow: 1"
          ref="_inner"
          node-key="id"
          :props="defaultProps"
          :data="data"
          :filter-node-method="_filterNode"
          :disabled="disabled"
          :expand-on-click-node="expandOnClickNode"
          :check-on-click-node="checkOnClickNode"
          :auto-expand-parent="autoExpandParent"
          :show-checkbox="showCheckbox"
          :default-expand-all="defaultExpandAll"
          :default-expanded-keys="defaultExpandedKeys"
          :check-strictly="checkStrictly"
          @node-click="_onNodeClick"
          @node-contextmenu="_nodeContextMenu"
          @check="_onCheck"
          @current-change="_onCurrentChange"
          @node-expand="_onNodeExpand"
          @node-collapse="_onNodeCollapse"
          @node-drag-start="_onDragStart"
          @node-drag-enter="_onDragEnter"
          @node-drag-leave="_onDragLeave"
          @node-drag-over="_onDragOver"
          @node-drag-end="_onDragEnd"
          @node-dra="_onDrag"
      >
        <template #default="{ node, data }">
          <div class="tree-node no-select"
               style="flex: 1;"
               @dblclick="evt=>_nodeDblClick(data,node,evt)"
               @mousedown="evt=>_mousedown(data,node,evt)">
            <span v-if="typeof getIconName === 'function'">
              <component :is="renderIcon(getIconName(node, data))" style="font-size: 18px;"/>
            </span>
            <span v-else-if="typeof data?.icon === 'string'">
              <component :is="renderIcon(data?.icon)" style="font-size: 18px;"/>
            </span>
            <span>{{ data.text }}</span>
          </div>
        </template>
      </el-tree>
    </el-scrollbar>
    <el-alert v-show="checkedCount>0" :title="`已勾选${checkedCount}项`" type="success" show-icon :closable="false"/>
    <Vue3Menus :open="menusOpen" :event="menusEvent" :menus="nodeContextMenu" :args="menusArgs">
      <template #icon="{ menu, index, activeIndex }">
        <component :is="renderIcon(menu.icon)" style="font-size: 18px;"/>
      </template>
    </Vue3Menus>
  </div>
</template>

<script>
import {nextTick, toRaw} from 'vue'
import {Vue3Menus} from 'vue3-menus'
import _ from "lodash"
import WebVjsonMix from "../WebVjsonMix"
import {renderIcon} from "../webutils"
import {Tree} from "../../Defaults"
import {analyzeParamsForWatch, buildTreeData, runEvent, calcObjectFlat} from "@/utils"
import YvI18nMixin from "@/components/YvI18nMixin";

export default {
  components: {
    Vue3Menus,
  },
  mixins: [WebVjsonMix, YvI18nMixin],
  emits: [
    'node-click',
    'current-change',
  ],
  data() {
    const vjson = _.defaults(this.vjson, Tree)
    const defaultProps = {
      children: 'children',
      label: 'text',
      nodeKey: 'id',
    }
    return {
      emptyText: vjson.emptyText,
      loading: false,
      checkedCount: 0,
      menusOpen: false,
      menusEvent: {},
      menusArgs: {},
      defaultProps,
      expandOnClickNode: vjson.expandOnClickNode ?? false,
      checkOnClickNode: vjson.checkOnClickNode ?? false,
      autoExpandParent: vjson.autoExpandParent ?? false,
      // 0: data, 1:dataSource-url, 2:dataSource-sql
      dataType: vjson.dataType || "0",
      _filterKeyword: '',
      enableFilter: vjson.enableFilter,
      enableRefresh: vjson.enableRefresh,
      enableExpandedAll: vjson.enableExpandedAll,
      showCheckbox: vjson.checkbox,
      disabled: vjson.disabled,
      hidden: vjson.hidden,
      checkStrictly: vjson.checkStrictly,
      defaultExpandAll: vjson.defaultExpandAll,
      defaultExpandedKeys: [],
      keepExpandKeys: vjson.keepExpandKeys ?? false,
      data: vjson.data,
      nodeContextMenu: vjson.nodeContextMenu || [],
      getIconName: vjson.getIconName,
      dataSource: vjson.dataSource,
      autoLoad: vjson.autoLoad ?? false,
    }
  },
  computed: {},
  watch: {
    _filterKeyword: {
      handler(newVal) {
        this.$refs._inner.filter(newVal);
      }
    },
  },
  mounted() {
    if (this.autoLoad && this.dataSource?.reload) {
      this.reload()
    }
    this.checkedCount = this.$refs._inner.getHalfCheckedKeys().length + this.$refs._inner.getCheckedKeys().length
  },
  destroyed() {
    if (this.dataSourceWatchStopHandle.length > 0) {
      _.forEach(this.dataSourceWatchStopHandle, (fn) => {
        fn()
      })
      this.dataSourceWatchStopHandle = []
    }
  },
  created() {
    const dataSource = _.cloneDeep(toRaw(this.dataSource))
    if (!dataSource) {
      return
    }
    const params = {}
    if (dataSource.method === 'invoke' || dataSource.sqlId) {
      if (dataSource.sqlId) {
        dataSource.method = 'invoke'
        dataSource.url = '/com/galaxis/wms/Dev@executeSQL'
        const scope = this.vcxt.scope
        if (!scope._relPath) {
          return null
        }
        if (_.startsWith(dataSource.sqlId, '@')) {
          // sqlId 是 @ 开头，代表直接带中文名
          // dataSource.url = relPath + dataSource.sqlId
          params.path = scope._relPath.split(".")[0]
          params.sqlId = dataSource.sqlId.substring(1)
          params.pagination = this.pagination
        } else {
          if (_.endsWith(_.toLower(scope._relPath), '.vue')) {
            params.path = scope._relPath.substring(0, scope._relPath.length - 4)
          } else {
            params.path = scope._relPath
          }
          params.sqlId = dataSource.sqlId
          params.pagination = false
        }
      }
    }
    const me = this
    this.dataSourceWatchStopHandle = analyzeParamsForWatch(me.vcxt.scope.$watch, this.dataSource.params, (watchResultForParam) => {
      console.log('watch params changed! reload tree')
      const style = window.getComputedStyle(this.$refs._inner.$el);
      if (style.display !== 'none') {
        // 没有隐藏，既刷新
        me.dataSource.reload(watchResultForParam)
      }
    })
    dataSource.reload = _.debounce(() => {
      runEvent(this.vcxt.scope, this.vjson, "onDataLoadBefore", {})
      const reParams = _.cloneDeep(params)

      // 从 params 参数描述中，分析出实际请求参数
      let cancel = false
      let p = calcObjectFlat(this.vcxt?.scope?.$data, this.dataSource.params, (express, value) => {
        // 判断参数，是否被 watch
        if ((express + '').startsWith('watch:{')) {
          value = '' + value
          if (value.startsWith('watch:')) {
            value = value.substring(6)
          }
        }

        // 判断参数，是否要求必填
        if ((express + '').endsWith('}:required')) {
          if (typeof value === 'undefined' || value == null) {
            console.debug('express:' + express + ' is ' + value + ', request canceled!')
            cancel = true
          }

          value = '' + value
          if (value.endsWith(':required')) {
            value = value.substring(0, value.length - 9)
          }

          if (!value) {
            console.debug('express:' + express + ' is ' + value + ', request canceled!')
            cancel = true
          }
        }
        return value
      })

      if (cancel) {
        me.setData([])
        if (this.vjson.reference) {
          _.set(this.vcxt.scope, this.vjson.reference + '.selection', null)
          _.set(this.vcxt.scope, this.vjson.reference + '.checkedNodes', [])
          _.set(this.vcxt.scope, this.vjson.reference + '.checkedKeys', [])
        }
        if (this.showCheckbox) {
          this.$refs._inner.setCheckedKeys([])
          this.$nextTick(() => {
            this.checkedCount = this.$refs._inner.getHalfCheckedKeys().length + this.$refs._inner.getCheckedKeys().length
          })
        }
        runEvent(this.vcxt.scope, this.vjson, "onDataLoadComplete", this, [], [])
        return
      }

      if (typeof this.dataSource.paramsFn === 'function') {
        const cp = this.dataSource.paramsFn()
        _.merge(p, cp)
      }
      _.merge(reParams, p)
      // 请求
      me.setLoading(true)
      const expandKeys = []
      if (this.keepExpandKeys) {
        const nodeDatas = this.$refs._inner.store.nodesMap
        for (let key in nodeDatas) {
          if (nodeDatas[key].expanded) {
            expandKeys.push(key)
          }
        }
      }
      system.invokeServer(dataSource.url, reParams).then(res => {
        if (res.success) {
          if (dataSource.noBuildTree) {
            runEvent(this.vcxt.scope, this.vjson, "onDataLoadComplete", this, res.data, res.data)
            me.setData(res.data)
            if (this.vjson.reference) {
              _.set(this.vcxt.scope, this.vjson.reference + '.selection', null)
              _.set(this.vcxt.scope, this.vjson.reference + '.checkedNodes', [])
              _.set(this.vcxt.scope, this.vjson.reference + '.checkedKeys', [])
            }
            if (this.showCheckbox) {
              this.$refs._inner.setCheckedKeys([])
              this.$nextTick(() => {
                this.checkedCount = this.$refs._inner.getHalfCheckedKeys().length + this.$refs._inner.getCheckedKeys().length
              })
            }

          } else {
            const checkedIds = []
            const treeData = buildTreeData(res.data, {
              idField: dataSource.idField ?? "id",
              parentField: dataSource.parentField ?? "parent_id",
              checkField: dataSource.checkField ?? "check",
              textField: dataSource.textField ?? "text",
              checkStrictly: this.checkStrictly,
            }, checkedIds)
            runEvent(this.vcxt.scope, this.vjson, "onDataLoadComplete", this, res.data, treeData)
            me.setData(treeData)
            if (this.showCheckbox) {
              this.$refs._inner.setCheckedKeys(checkedIds)
              this.$nextTick(() => {
                this.checkedCount = this.$refs._inner.getHalfCheckedKeys().length + this.$refs._inner.getCheckedKeys().length
              })
            }
            if (this.vjson.reference) {
              _.set(this.vcxt.scope, this.vjson.reference + '.selection', null)
              _.set(this.vcxt.scope, this.vjson.reference + '.checkedNodes', [])
              _.set(this.vcxt.scope, this.vjson.reference + '.checkedKeys', checkedIds)
            }
            if (this.keepExpandKeys) {
              this.$nextTick(() => {
                const nodeDatas = this.$refs._inner.store.nodesMap
                for (let key in nodeDatas) {
                  if (expandKeys.includes(key)) {
                    nodeDatas[key].expanded = true
                  }
                }
              })
            }
          }
        }
      }).catch((e) => {
        runEvent(this.vcxt.scope, this.vjson, "onDataLoadComplete", this, [], [])
        console.error(e)
        system.showErrorDialog(e.toString())

      }).finally(() => {
        me.setLoading(false)
      })
    }, 10)
    this.dataSource = dataSource
  },
  methods: {
    renderIcon,
    setLoading(isLoad) {
      this.loading = isLoad
    },
    reload() {
      if (typeof this.dataSource.reload === 'function') {
        this.dataSource.reload()
      }
    },
    _nodeContextMenu(evt, node, data) {
      // const menuOption = toRaw(this.nodeContextMenu)
      evt.preventDefault()
      this.menusOpen = false;
      nextTick(() => {
        this.menusEvent = evt
        this.menusArgs = {event: evt, node, data}
        this.menusOpen = true
      })
    },
    _nodeDblClick(data, node, evt) {
      if (!node.disabled && !node.isLeaf) {
        if (node.expanded) {
          node.collapse()
        } else {
          node.expand()
        }

        evt.preventDefault()
        evt.stopPropagation()
      }
    },
    _mousedown(data, node, evt) {
      if (evt.button === 2) {
        this.$emit('contextMenu', data, node)
      }
    },
    _onCheck(data, {checkedKeys, checkedNodes, halfCheckedKeys, halfCheckedNodes}) {
      if (this.vjson.reference) {
        _.set(this.vcxt.scope, this.vjson.reference + '.checkedNodes', checkedNodes)
        _.set(this.vcxt.scope, this.vjson.reference + '.checkedKeys', checkedKeys)
      }
      runEvent(this.vcxt.scope, this.vjson, "check", {checkedKeys, checkedNodes, halfCheckedKeys, halfCheckedNodes})
      this.checkedCount = this.$refs._inner.getHalfCheckedKeys().length + this.$refs._inner.getCheckedKeys().length
    },
    _onNodeClick(data, b, c, d) {
      this.menusOpen = false;
      this.$emit('node-click', data, b, c, d)
      runEvent(this.vcxt.scope, this.vjson, "nodeClick", ...arguments)
    },
    _onCurrentChange() {
      _.set(this.vcxt.scope, this.vjson.reference + '.selection', arguments[0])
      runEvent(this.vcxt.scope, this.vjson, "currentChange", ...arguments)
    },
    _onNodeExpand() {
      runEvent(this.vcxt.scope, this.vjson, "nodeExpand", ...arguments)
    },
    _onNodeCollapse() {
      runEvent(this.vcxt.scope, this.vjson, "nodeCollapse", ...arguments)
    },
    _onDragStart() {
      runEvent(this.vcxt.scope, this.vjson, "nodeDragStart", ...arguments)
    },
    _onDragEnter() {
      runEvent(this.vcxt.scope, this.vjson, "nodeDragEnter", ...arguments)
    },
    _onDragLeave() {
      runEvent(this.vcxt.scope, this.vjson, "nodeDragLeave", ...arguments)
    },
    _onDragOver() {
      runEvent(this.vcxt.scope, this.vjson, "nodeDragOver", ...arguments)
    },
    _onDragEnd() {
      runEvent(this.vcxt.scope, this.vjson, "nodeDragEnd", ...arguments)
    },
    _onDrag() {
      runEvent(this.vcxt.scope, this.vjson, "nodeDrag", ...arguments)
    },
    setData(data) {
      this.data = data
    },
    getNode(key) {
      return this.$refs._inner.getNode(key)
    },
    getCheckedNodes() {
      return this.$refs._inner.getCheckedNodes()
    },
    getCheckedKeys() {
      return this.$refs._inner.getCheckedKeys()
    },
    setCheckedKeys(keys) {
      return this.$refs._inner.setCheckedKeys(keys)
    },
    getCurrentNode() {
      return this.$refs._inner.getCurrentNode();
    },
    getCurrentKey() {
      return this.$refs._inner.getCurrentKey();
    },
    setCurrentKey(key) {
      return this.$refs._inner.setCurrentKey(key);
    },
    expandedAll(expanded = true) {
      const nodeDatas = this.$refs._inner.store.nodesMap
      for (let key in nodeDatas) {
        nodeDatas[key].expanded = expanded;
      }
      // const data = this.data ?? [];
      // const allNode = []
      // const getAllKeys = (nodes) => {
      //   _.forEach(nodes, node => {
      //     allNode.push(node)
      //     if (node.children) getAllKeys(node.children)
      //   })
      // }
      // getAllKeys(data);
      // allNode.forEach(node => (node.expanded = expanded))
    },

    setExpandedKeys(keys) {
      this.defaultExpandedKeys = keys;
    },

    _filterNode(value, data) {
      if (!value) return true;

      value = value.toLowerCase();
      const text = data.text;
      if (!text) return true;
      return (
          text.toLowerCase()
              .indexOf(value) >= 0 || text.toLowerCase().indexOf(value) >= 0
      );
    },
  }
}
</script>

<style lang="less">
.el-tree {
  .el-tree-node:focus > .el-tree-node__content {
    background-color: #d9ecf8;
  }

  .el-tree-node.is-current {
    background-color: #d9ecf8;
  }
}

.yv-tree-wrapper {
  display: flex;
  flex-direction: column;

  & > .el-row {
    display: flex;
    flex-direction: row;

    & > .el-input {
      flex: 1;
      /*margin-right: 5px*/
    }

    & > div:last-child.el-input {
      margin-right: 0
    }
  }

  .el-tree-node.is-current {
    background-color: #d9ecf8;
  }
}
</style>